Parallelize Soroban pre-apply step. - #5402
Conversation
b7896a9 to
be994a5
Compare
There was a problem hiding this comment.
Pull request overview
Parallelizes Soroban pre-apply validation while preserving serial ledger mutations.
Changes:
- Splits pre-apply into parallel read-only and sequential write phases.
- Adds a ledger view combining classic-phase changes with LCL state.
- Adds concurrency and signer/sequence regression tests with updated baselines.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
test-tx-meta-baseline-next/InvokeHostFunctionTests.json |
Updates next-protocol test baselines. |
test-tx-meta-baseline-current/InvokeHostFunctionTests.json |
Updates current-protocol test baselines. |
src/util/BatchExecutor.h |
Exposes preferred parallelism controls. |
src/util/BatchExecutor.cpp |
Computes preferred worker count. |
src/transactions/TransactionFrameBase.h |
Splits the pre-apply interface. |
src/transactions/TransactionFrame.h |
Declares read/write pre-apply paths. |
src/transactions/TransactionFrame.cpp |
Implements separated pre-apply processing. |
src/transactions/test/TransactionTestFrame.h |
Adapts the test transaction interface. |
src/transactions/test/TransactionTestFrame.cpp |
Delegates split pre-apply calls. |
src/transactions/test/SorobanTxTestUtils.cpp |
Corrects footprint deduplication. |
src/transactions/test/ParallelApplyTest.cpp |
Tests worker-count independence. |
src/transactions/test/InvokeHostFunctionTests.cpp |
Adds pre-apply regression coverage. |
src/transactions/ParallelApplyUtils.h |
Declares parallel pre-apply orchestration. |
src/transactions/ParallelApplyUtils.cpp |
Runs parallel reads and serial writes. |
src/transactions/FeeBumpTransactionFrame.h |
Splits fee-bump pre-apply APIs. |
src/transactions/FeeBumpTransactionFrame.cpp |
Implements fee-bump pre-apply phases. |
src/ledger/ImmutableLedgerView.h |
Defines the pre-apply ledger view. |
src/ledger/ImmutableLedgerView.cpp |
Implements combined ledger lookups. |
Suppressed comments (3)
src/transactions/ParallelApplyUtils.cpp:442
- Cap the task count at the number of bundles.
BatchExecutor::ensureWorkerscreates and retains one worker per submitted task, so a small ledger on a many-core host currently starts dozens of workers whose ranges are empty, adding startup and wake-up overhead to the path this PR is optimizing.
size_t taskCount = app.getBatchExecutor().preferredTaskCount();
src/transactions/ParallelApplyUtils.cpp:400
- This rationale has the direction backwards: removing a signer decreases the owner's sub-entry count (and, when sponsored, the sponsor's sponsoring count), which can only increase available balance. State that monotonic effect explicitly because it is the reason deferring these writes cannot invalidate another transaction.
// - The removal of one-time pre-authorized tx signers - this is also fine
// because any given transaction in a ledger is unique, and increasing the
// sub-entry count of a source/sponsor account is not relevant at that
// point, as the fees have already been successfully charged.
src/transactions/ParallelApplyUtils.cpp:391
- Correct the typo “separatation” to “separation”.
// This phase separatation hinges on the fact that the validation outcome
6f7c2f1 to
4cdc91b
Compare
d7c7629 to
02b730f
Compare
02b730f to
41f8b63
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Suppressed comments (1)
src/util/BatchExecutor.cpp:226
- Ceiling the range size does not reliably create
numTasksranges: for example,count=4, numTasks=3creates only two tasks. This underutilizes available cores in the performance-sensitive path and contradicts the API contract. Distribute the remainder across exactlynumTasksranges instead.
auto rangeSize = (count + numTasks - 1) / numTasks;
std::vector<std::function<int()>> tasks;
tasks.reserve(numTasks);
for (size_t begin = 0; begin < count; begin += rangeSize)
41f8b63 to
1d2b7d4
Compare
1d2b7d4 to
7d5a01b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/util/BatchExecutor.cpp:223
- This ceiling-division expression can overflow
size_t. For example,count == SIZE_MAXandnumTasks == 2producesrangeSize == 0, so the following loop never advances; even with a safe quotient,begin + rangeSizealso needs overflow protection. Compute the quotient/remainder without addition overflow and advance via a clampedend.
auto rangeSize = (count + numTasks - 1) / numTasks;
While the current logic intertwines reads and writes, in fact it can be cleanly separated into a read-only validation step, and a sequential commit step that simply bumps the sequence numbers and removes pre-authorized tx signers. This is possible because that while the writes change the entries that take part in validation, none of these changes are relevant during the validation. Specifically, sequence number bump is only observable by a single transaction (the one that has the respective account as a source), and the pre-authorized tx signer by definition belongs to a single transaction. There is also a subtle caveat to the latter operation: it increases the available balance of the signer owner (or its sponsor), but since at the pre-apply time the fees have already been charged, we're only checking that the account available balance is non-negative, which is an invariant that must always hold in the current protocol. The change is not protocol-gated because it's not a protocol change for the *current* protocol. It was technically a protocol change prior to p26 where we had a bug that actually did allow overcharging the fee bump source accounts and thus making their available balance to go negative. However, the bug has been fixed without the behavior ever triggering on-chain, and thus this replay-only behavior change should be non-observable. This change significantly speeds up the pre-apply step. On the local high TPL benchmarks I'm getting 30-60ms improvement locally compared to the main branch version.
7d5a01b to
8958c27
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/transactions/FeeBumpTransactionFrame.cpp:122
- The new parallel write path removes the fee-source pre-auth signer before delegating to the inner transaction, but the added Soroban tests only fee-bump transactions whose fee source has no pre-auth signer; existing one-time-signer coverage exercises the old sequential
applypath. Please add a p23+ parallel test that installs the fee-bump hash as a signer on the fee source (including sponsorship if supported) and verifies its removal and metadata.
LedgerTxn ltxTx(ltx);
removeOneTimeSignerKeyFromFeeSource(ltxTx);
meta.pushTxChangesBefore(ltxTx);
ltxTx.commit();
}
mInnerTx->preParallelApplyWrite(app, ltx, meta, txResult);
Note
Copilot is running an experiment and ran this review at Lite.
Description
While the current logic intertwines reads and writes, in fact it can be cleanly separated into a read-only validation step, and a sequential commit step that simply bumps the sequence numbers and removes pre-authorized tx signers. This is possible because that while the writes change the entries that take part in validation, none of these changes are relevant during the validation. Specifically, sequence number bump is only observable by a single transaction (the one that has the respective account as a source), and the pre-authorized tx signer by definition belongs to a single transaction. There is also a subtle caveat to the latter operation: it increases the available balance of the signer owner (or its sponsor), but since at the pre-apply time the fees have already been charged, we're only checking that the account available balance is non-negative, which is an invariant that must always hold in the current protocol.
The change is not protocol-gated because it's not a protocol change for the current protocol. It was technically a protocol change prior to p26 where we had a bug that actually did allow overcharging the fee bump source accounts and thus making their available balance to go negative. However, the bug has been fixed without the behavior ever triggering on-chain, and thus this replay-only behavior change should be non-observable.
This change significantly speeds up the pre-apply step. On the local high TPL benchmarks I'm getting 30-60ms improvement locally compared to the main branch version.
This PR also adds test coverage for the edge case pre-apply interactions that weren't covered well (or at all), such as pre-auth signer removal, tx invalidation due to sequence bumps etc. This also contains a randomized acceptance test that ensures that parallel pre-apply logic is deterministic across any number of worker threads.
Checklist
clang-formatv8.0.0 (viamake formator the Visual Studio extension)